package run.halo.app.service.front.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import java.io.File;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import me.chanjar.weixin.common.error.WxErrorException;
import me.zhyd.oauth.model.AuthUser;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import run.halo.app.common.R;
import run.halo.app.model.dto.front.BindMobileDTO;
import run.halo.app.model.dto.front.LoginDTO;
import run.halo.app.model.dto.front.MobileMsgDTO;
import run.halo.app.model.entity.front.Customer;
import run.halo.app.model.support.FrontConstant;
import run.halo.app.security.token.AuthToken;
import run.halo.app.service.front.CustomerService;
import run.halo.app.service.front.FrontService;
import run.halo.app.utils.HaloUtils;

@Service
public class FrontServiceImpl implements FrontService {

    private final static int DEF_NUM = 1010110;
    private final CustomerService customerService;
    private final StringRedisTemplate redisTemplate;
    private WxMaService wxMaService;
    //登录用户过期时间 7 天
    private static final int expr_time = 3600 * 24 * 7;

    public FrontServiceImpl(CustomerService customerService,
        StringRedisTemplate redisTemplate,WxMaService wxMaService) {
        this.customerService = customerService;
        this.redisTemplate = redisTemplate;
        this.wxMaService = wxMaService;
    }

    @Override
    public Customer saveCustomer(AuthUser user) {
        DateTime currDate = DateUtil.date();

        Customer newCustomer = new Customer();
        BeanUtil.copyProperties(user, newCustomer);
        // TODO 密码加密处理暂未实现，本期只实现短信登录和微信扫码登录
        String password = RandomStringUtils.random(20);
        newCustomer.setUsername("wx_" + (System.currentTimeMillis()^DEF_NUM));
        newCustomer.setPassword(password);

        return customerService.create(newCustomer);
    }

    @Override
    public AuthToken cacheAuthCustomer(Customer customer) {
        try {
            // Generate new token
            AuthToken token = new AuthToken();

            token.setAccessToken(HaloUtils.randomUUIDWithoutDash());
            token.setExpiredIn(expr_time);
            token.setRefreshToken(HaloUtils.randomUUIDWithoutDash());
            redisTemplate.opsForValue()
                .set(FrontConstant.REDIS_CUSTOMER_AUTH_TOKEN + token.getAccessToken(),
                    JSON.toJSONString(customer),
                    expr_time,
                    TimeUnit.SECONDS);
            return token;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    @Transactional
    public R<AuthToken> checkMobileCode(MobileMsgDTO msgDTO) {
        String code = redisTemplate.opsForValue()
            .get(FrontConstant.REDIS_MSG_CODE_KEY + msgDTO.getPhoneNo());
        //校验验证码
        if (msgDTO.getCode().equals(code)) {
            Optional<Customer> mobilUser =
                customerService.findByPhoneNumber(msgDTO.getPhoneNo());
            //如果系统没有用户，则初始化一个新用户存入数据库，返回初始化的用户
            Customer customer;
            if (mobilUser.isEmpty()) {
                customer = initCustomer(msgDTO.getPhoneNo());
            } else {
                customer = mobilUser.get();
            }
            return R.ok(cacheAuthCustomer(customer));
        } else {
            return R.fail("验证码错误！");
        }
    }

    @Override
    public R<AuthToken> bindMobile(BindMobileDTO bindMobileDTO) {
        String code = redisTemplate.opsForValue()
            .get(FrontConstant.REDIS_MSG_CODE_KEY + bindMobileDTO.getPhoneNo());
        if (bindMobileDTO.getCode().equals(code)) {
            //1、看UUID有没有在数据库中有对应的数据
            Optional<Customer> uuidUser = customerService.findByUuid(bindMobileDTO.getUuid());
            //2、如果有对应的数据
            if (!uuidUser.isEmpty()) {
                Customer oauth2Customer = uuidUser.get();
                oauth2Customer.setPhoneNumber(bindMobileDTO.getPhoneNo());
                //看看手机号有没有对应的用户信息
                Optional<Customer> mobilUser =
                    customerService.findByPhoneNumber(bindMobileDTO.getPhoneNo());
                //存在手机号记录，许先删除手机号记录再绑定
                if(!mobilUser.isEmpty()){
                    customerService.remove(mobilUser.get());
                }
                customerService.update(oauth2Customer);
                return R.ok(cacheAuthCustomer(oauth2Customer));
            }
            //如果没有对应uuid数据，走一次短信登录流程先让用户登录。
            return checkMobileCode(bindMobileDTO);
        }else {
            return R.fail("验证码错误！");
        }
    }

    /**
     * 微信登录, openId,unionId,mobile 三者数据关系,两种逻辑关系
     * 不能拿到用户手机号：不允许登录
     * 拿到用户手机号：先判断unionId 不存在,创建账号, 暂时不用考虑openId
     * @param dto
     * @return
     */
    @Override
    public R<AuthToken> miniAppLogin(LoginDTO dto) {
        Optional<Customer> byUuid = Optional.empty();
        try {
            WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(dto.getCode());

            if (StringUtils.hasText(dto.getMobileCode())){
                // 获取到对应手机号
                WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(dto.getMobileCode());

                /*WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionInfo.getSessionKey(), dto.getEncryptedData(), dto.getIvStr());
                System.out.println(userInfo);*/
                //看看手机号有没有对应的用户信息
                byUuid = customerService.findByPhoneNumber(phoneNoInfo.getPhoneNumber());

                // 现有用户不存在什么权限, 可以替换?
                if (byUuid.isPresent() && StringUtils.hasText(sessionInfo.getUnionid())){
                    Customer customer = byUuid.get();
                    customer.setUuid(sessionInfo.getUnionid());
                    customer.setOpenId(sessionInfo.getOpenid());
                    customer.setUpdateTime(new DateTime());
                    customerService.update(customer);
                } else if (byUuid.isEmpty()){
                    Customer customer = initCustomer(phoneNoInfo.getPhoneNumber(),sessionInfo.getUnionid(),sessionInfo.getOpenid());
                    byUuid = Optional.of(customer);
                }

            } else {
                byUuid = customerService.findByUuid(sessionInfo.getUnionid());
                if (byUuid.isEmpty()){
                    return R.fail();
                }
            }

            if (StringUtils.hasText(dto.getUuid())){
                String token = HaloUtils.randomUUIDWithoutDash();
                redisTemplate.opsForValue().set(FrontConstant.CHECK_LOGIN.concat(dto.getUuid())
                    ,token, 5000,TimeUnit.MILLISECONDS);

                redisTemplate.opsForValue().set(FrontConstant.REDIS_CUSTOMER_AUTH_TOKEN.concat(token)
                    ,JSON.toJSONString(byUuid.get()), expr_time,TimeUnit.SECONDS);
            }

        } catch (WxErrorException e) {
            throw new RuntimeException(e);
        }

        return R.ok();
    }

    @Override
    public R<String> createWxaCode(String path) {

        try {
            byte[] bytes = wxMaService.getQrcodeService().createQrcodeBytes(path, 430);
            return R.ok("data:image/png;base64,"+Base64.encode(bytes));
        } catch (WxErrorException e) {
            e.printStackTrace();
        }
        return R.fail();
    }

    @Override
    public R<AuthToken> checkLogin(String uuid) {
        String code = redisTemplate.opsForValue()
            .get(FrontConstant.CHECK_LOGIN.concat(uuid));
        if (StringUtils.hasText(code)){
            redisTemplate.delete(FrontConstant.CHECK_LOGIN.concat(uuid));
            AuthToken token = new AuthToken();
            token.setExpiredIn(expr_time);
            token.setAccessToken(code);
            token.setRefreshToken(code);
            return R.ok(token);
        }
        return R.fail();
    }


    /**
     * 初始化手机用户
     *
     * @param phoneNumber
     * @return
     */
    protected Customer initCustomer(String phoneNumber) {
        return initCustomer(phoneNumber,null,null);
    }

    protected Customer initCustomer(String phoneNumber,String uuid,String openId) {
        DateTime currDate = DateUtil.date();

        Customer customer = new Customer();
        customer.setPhoneNumber(phoneNumber);
        if (StringUtils.hasText(uuid)){
            customer.setUuid(uuid);
            customer.setOpenId(openId);
        }else{
            customer.setUuid(StrUtil.uuid());
        }

        customer.setUsername("wx_" + (System.currentTimeMillis()^DEF_NUM));
        customer.setNickname(customer.getUsername());
        customer.setPassword(RandomStringUtils.random(20));
        customer.setCreateTime(currDate);
        customer.setUpdateTime(currDate);

        return customerService.create(customer);
    }
}
